package com.junxonline.instant.terminal.netty.handler;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.junxonline.instant.common.constant.CommonConstant;
import com.junxonline.instant.common.constant.RedisMqChannelConstant;
import com.junxonline.instant.common.constant.TerminalConnectTypeConstant;
import com.junxonline.instant.common.util.common.RedisUtils;
import com.junxonline.instant.common.util.terminal.NettyChannelUtils;
import com.junxonline.instant.terminal.config.NettyConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 握手回调事件处理器
 *
 * @author JunX
 * @date 2021-09-08
 */
@Log4j2
@Component
@Sharable
public class HandshakeEventHandler extends ChannelInboundHandlerAdapter {

    @Autowired
    private NettyConfig nettyConfig;

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 主动断开连接定时任务
     */
    public final static AttributeKey<ScheduledFuture<ChannelFuture>> DISCONNECTION_SCHEDULE_FUTURE = AttributeKey.valueOf("DisconnectionScheduledFuture");

    /**
     * 握手请求事件回调
     *
     * @param context 上下文
     * @param event   事件
     * @throws Exception Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext context, Object event) throws Exception {
        // 握手成功事件
        if (event instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            log.debug("客户端 -> " + context.channel().remoteAddress() + " 握手成功" + " -> " + event);
        } else {
            super.userEventTriggered(context, event);
        }
    }

    /**
     * 连接加入
     *
     * @param context 上下文
     * @throws Exception Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext context) throws Exception {
        log.info("客户端 -> " + context.channel().remoteAddress() + " 连接加入");
        // 启动一个定时任务在心跳时间后关闭连接
        ScheduledFuture<ChannelFuture> future = context.executor().schedule(() -> {
            log.warn("客户端 -> {} 超时未认证 连接已关闭", context.channel().remoteAddress());
            return context.channel().close();
        }, nettyConfig.getHeartBeatTime(), TimeUnit.SECONDS);
        // 将任务句柄和Channel绑定在一起
        context.channel().attr(DISCONNECTION_SCHEDULE_FUTURE).set(future);
        log.debug("为客户端 -> {} 创建了{}秒后主动关闭连接的定时任务", context.channel().remoteAddress(), nettyConfig.getHeartBeatTime());
        super.channelActive(context);
    }

    /**
     * 连接断开
     *
     * @param context 上下文
     * @throws Exception Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext context) throws Exception {
        log.info("客户端 -> " + context.channel().remoteAddress() + " 断开连接");
        String terminalCode = NettyChannelUtils.getTerminalCode(context.channel().id());
        // 已认证加入容器的终端通过Redis消息队列刷新离线状态
        if (StrUtil.isNotBlank(terminalCode)) {
            Map<String, Object> statusDataMap = new HashMap<>();
            statusDataMap.put("terminalCode", terminalCode);
            statusDataMap.put("status", CommonConstant.FAIL);
            // 将终端编号发送至Redis消息队列中处理
            redisUtils.convertAndSend(RedisMqChannelConstant.KEY_REDIS_MSG_CHANNEL_TERMINAL_STATUS, JSON.toJSONString(statusDataMap));
            // 移除容器中的Channel
            NettyChannelUtils.removeChannel(context.channel());
            // 连接日志插入 将终端编号与状态发送至Redis消息队列中处理
            Map<String, Object> connectDataMap = new HashMap<>();
            connectDataMap.put("terminalCode", terminalCode);
            connectDataMap.put("type", TerminalConnectTypeConstant.OFFLINE);
            redisUtils.convertAndSend(RedisMqChannelConstant.KEY_REDIS_MSG_CHANNEL_LOG_DEVICE_CONNECT, JSON.toJSONString(connectDataMap));
        }
        context.channel().close();
        // 取消关闭连接的定时任务
        ScheduledFuture<ChannelFuture> future = context.channel().attr(DISCONNECTION_SCHEDULE_FUTURE).getAndSet(null);
        if (future != null) {
            future.cancel(true);
            log.info("取消了客户端 -> {} 主动关闭连接的定时任务", context.channel().remoteAddress());
        }
        super.channelInactive(context);
    }

}
